#include "CollectRefs.h"
#include <iostream>
using namespace std;

CollectRefs::CollectRefs(analyzeTraversal *set_all) {
    all = set_all;
    last = NULL;

    references = NULL;
    n_references = 0;

    current_ref = NULL;
}

void CollectRefs::addReference(Reference *r) {
    references = (Reference **)realloc(references, sizeof(Reference *) * (n_references+1));
    references[n_references] = r;
    n_references = n_references + 1;
}

void CollectRefs::replaceReference(Reference *r) {
    references[n_references-1] = r;
}

void CollectRefs::removeReference(Reference *r) {
    references = (Reference **)realloc(references, sizeof(Reference *) * (n_references-1));
    references[n_references-2] = r;
    n_references--;
}

void CollectRefs::preOrderVisit(SgNode *node) {
}

VariableWrapper *CollectRefs::resolveVariable(SgVarRefExp *var) {
    VariableWrapper *result = NULL;
    SgDeclarationStatement *decl = all->resolveSymbol(var);
    if(decl != NULL) { 
        SgFunctionParameterList *params = isSgFunctionParameterList(decl);
        if(params != NULL) {
            string name = var->get_symbol()->get_name().str();
            VariableWrapper *new_var = all->getCurrentFunction()->findVarByName(name);
            if(new_var != NULL) {
                result = new_var;
            }
        } else {
            VariableWrapper *new_var = all->getCurrentFunction()->findVarByDecl(decl);
            if(new_var != NULL) {
                result = new_var;
            } 
        }
    }
    return result;
}

int CollectRefs::canIgnore(SgNode *node) {
    if(isSgValueExp(node) != NULL) return 1;
    return 0;
}

int CollectRefs::dontReset(SgNode *node) {
    if(current_ref == 0) return 0;
    if(current_ref->is_var() || current_ref->is_ptr()) {
        VariableWrapper *wrapper = current_ref->getVar();
        if(wrapper->getOrder() > current_ref->getOrder()) return 1;
    }
    
    return 0;
}

void CollectRefs::postOrderVisit(SgNode *node) {
    SgVarRefExp *var_ref = isSgVarRefExp(node);
    SgPointerDerefExp *deref_exp = isSgPointerDerefExp(node);
    SgDotExp *dot_exp = isSgDotExp(node);
    SgArrowExp *arrow = isSgArrowExp(node);

    if(canIgnore(node) == 1) {
        return;
    }

    if(var_ref != NULL) {

        // if this is a field name, don't process it
        if(isSgArrowExp(var_ref->get_parent()) != NULL && var_ref->get_parent()->get_childIndex(node) == 1) {
            return;
        }

        if(current_ref != NULL) {
            cout<<"Clearing out reference "<<current_ref->is_ptr()<<" "<<current_ref->is_struct()<<" "<<current_ref->is_var()<<current_ref->getVar()->str()<<" "<<var_ref->get_symbol()->get_name().str()<<endl;
            addReference(current_ref);
        }

        Reference *ref = new Reference(VARIABLE);
        VariableWrapper *var = resolveVariable(var_ref);
        ref->setVar(var);
        current_ref = ref;
    } else if(deref_exp != NULL) {
        if(current_ref != NULL) {
            Reference *ref = new Reference(POINTER);
            ref->setVar(current_ref->getVar());
            ref->setChild(current_ref);
            current_ref = ref;
        }
    } else if(dot_exp != NULL) {
        if(current_ref != NULL) {
            SgVarRefExp *field_ref = isSgVarRefExp(dot_exp->get_rhs_operand());
            Reference *ref = new Reference(STRUCT);
            ref->setField(field_ref->get_symbol()->get_name().str());
            ref->setVar(current_ref->getVar());
            ref->setChild(current_ref);
            current_ref = ref;
        }
    }else if(arrow != NULL) {
        if(current_ref != NULL) {
            Reference *ptr_ref = new Reference(POINTER);
            Reference *st_ref = new Reference(STRUCT);

            ptr_ref->setVar(current_ref->getVar());
            ptr_ref->setChild(current_ref);
            current_ref = ptr_ref;

            SgVarRefExp *field_ref = isSgVarRefExp(arrow->get_rhs_operand());
            st_ref->setVar(current_ref->getVar());
            st_ref->setField(field_ref->get_symbol()->get_name().str());
            st_ref->setChild(current_ref);
            current_ref = st_ref;
        }
    } else {
        if(dontReset(node) == 1) { return; }
        if(current_ref != NULL) addReference(current_ref);
        current_ref = NULL;
    }

}

void CollectRefs::finish() {
    if(current_ref != NULL ) {
        addReference(current_ref);
    }
}
